﻿using System;
using System.IO;
using System.Reflection;
using PluginInterface;
using System.Windows.Forms;
using System.Diagnostics;
using Buccaneer.Types;
using System.Collections.Generic;
using Buccontrols;
using System.Collections;

namespace Buccaneer
{
    /// <summary>
    /// Summary description for PluginServices.
    /// </summary>
    public class PluginServices : IPluginHost   //<--- Notice how it inherits IPluginHost interface!
    {
        #region Private Attributes

        private List<PluginAction> actionFunctions;
        private List<String> actionNames;
        private Dictionary<string, PluginAction> currentMenuActions;
        private Dictionary<string, string> typesLibraries;
        private Dictionary<string, AvailablePlugin> availablePlugins;

        #endregion

        /// <summary>
        /// Constructor of the Class
        /// </summary>
        public PluginServices()
        {
            actionNames = new List<String>();
            actionFunctions = new List<PluginAction>();
            currentMenuActions = new Dictionary<string, PluginAction>();
            typesLibraries = new Dictionary<string, string>();
            availablePlugins = new Dictionary<string, AvailablePlugin>();
        }

        //public event EventHandler Navigating;

        private AvailablePlugins colAvailablePlugins = new AvailablePlugins();

        /// <summary>
        /// A Collection of all Plugins Found and Loaded by the FindPlugins() Method
        /// </summary>
        public Dictionary<string, AvailablePlugin> AvailablePlugins
        {
            get { return availablePlugins; }
            set { availablePlugins = value; }
        }

        public string[] AvailablePluginTypes
        {
            get 
            { 
                string[] retval = new string[this.typesLibraries.Count];
                this.typesLibraries.Keys.CopyTo(retval, 0);
                return retval;
            }
        }

        /// <summary>
        /// Searches the Application's Plugins' Directory for Plugins
        /// </summary>
        public void FindPlugins()
        {
            FindPlugins(Options.PluginsFolder);
        }
        /// <summary>
        /// Searches the passed Path for Plugins
        /// </summary>
        /// <param name="Path">Directory to search for Plugins in</param>
        public void FindPlugins(string Path)
        {
            //First empty the collection, we're reloading them all
            colAvailablePlugins.Clear();

            //Go through all the files in the plugin directory
            foreach (string fileOn in Directory.GetFiles(Path))
            {
                FileInfo file = new FileInfo(fileOn);

                //Preliminary check, must be .dll
                if (file.Extension.Equals(".dll"))
                {
                    //Add the 'plugin'
                    this.AddPlugin(fileOn);
                }
            }
        }

        /// <summary>
        /// Unloads and Closes all AvailablePlugins
        /// </summary>
        public void ClosePlugins()
        {
            foreach (Types.AvailablePlugin pluginOn in colAvailablePlugins)
            {
                //Close all plugin instances
                //We call the plugins Dispose sub first incase it has to do 
                //Its own cleanup stuff
                pluginOn.Instance.Dispose();

                //After we give the plugin a chance to tidy up, get rid of it
                pluginOn.Instance = null;
            }

            //Finally, clear our collection of available plugins
            colAvailablePlugins.Clear();
        }

        public void AddPlugin(string FileName)
        {
            try
            {
                //Create a new assembly from the plugin file we're adding..
                Assembly pluginAssembly = Assembly.LoadFrom(FileName);

                //Next we'll loop through all the Types found in the assembly
                foreach (Type pluginType in pluginAssembly.GetTypes())
                {
                    if (pluginType.IsPublic) //Only look at public types
                    {
                        if (!pluginType.IsAbstract)  //Only look at non-abstract types
                        {
                            //Gets a type object of the interface we need the plugins to match
                            Type typeInterface = pluginType.GetInterface("PluginInterface.IPlugin", true);

                            //Make sure the interface we want to use actually exists
                            if (typeInterface != null)
                            {
                                //Add plugin info to dictionary to be able to check where every plugin can be loaded from
                                this.typesLibraries.Add(pluginType.Namespace, FileName);

                                Options.WriteComment("(i) Registered LibType: " + pluginType.Namespace);
                            }

                            typeInterface = null; //Mr. Clean			
                        }
                    }
                }
                pluginAssembly = null; //more cleanup
            }
            catch (Exception ex)
            {
                Options.WriteComment("(!) Error in loading plugin: " + FileName);
                Options.WriteComment("(i) " + ex.Message);
            }

        }

        public AvailablePlugin LoadPlugin(string type)
        {
            string FileName = this.typesLibraries[type];
            try
            {
                //Create a new assembly from the plugin file we're adding..
                Assembly pluginAssembly = Assembly.LoadFrom(FileName);
                Types.AvailablePlugin newPlugin = null;

                //Next we'll loop through all the Types found in the assembly
                foreach (Type pluginType in pluginAssembly.GetTypes())
                {
                    if (pluginType.IsPublic) //Only look at public types
                    {
                        if (!pluginType.IsAbstract)  //Only look at non-abstract types
                        {
                            //Gets a type object of the interface we need the plugins to match
                            Type typeInterface = pluginType.GetInterface("PluginInterface.IPlugin", true);

                            //Make sure the interface we want to use actually exists
                            if (typeInterface != null)
                            {
                                if (pluginType.Namespace == type)
                                {
                                    //Create a new available plugin since the type implements the IPlugin interface
                                    newPlugin = new Types.AvailablePlugin();

                                    //Set the filename where we found it
                                    newPlugin.AssemblyPath = FileName;

                                    //Create a new instance and store the instance in the dictionary
                                    newPlugin.Instance = (IPlugin)Activator.CreateInstance(pluginAssembly.GetType(pluginType.ToString()));

                                    //Set the Plugin's host to this class which inherited IPluginHost
                                    newPlugin.Instance.Host = this;

                                    int i = availablePlugins.Count + 1;
                                    while (availablePlugins.ContainsKey(type + i.ToString())) i--;

                                    newPlugin.Name = type + i.ToString();
                                    
                                    this.availablePlugins.Add(newPlugin.Name, newPlugin);

                                    Options.WriteComment("(i) Loaded Plugin: " + newPlugin.Name);
                                    
                                    //Mr. Clean
                                    newPlugin = null;
                                    typeInterface = null;
                                    pluginAssembly = null;

                                    return this.availablePlugins[type + i.ToString()];
                                }
                            }
                        }
                    }
                }
                return null;
            }
            catch (Exception ex)
            {
                Options.WriteComment("(!) Error in loading plugin: " + FileName);
                Options.WriteComment("(i) " + ex.Message);
            }
            return null;
        }

        public AvailablePlugin GetPlugin(string name)
        {
            return this.AvailablePlugins[name];
        }

        public void UnloadPlugin(string name)
        {
            this.availablePlugins.Remove(name);
        }

        /// <summary>
        /// Displays a feedback dialog from the plugin
        /// </summary>
        /// <param name="Feedback">String message for feedback</param>
        /// <param name="Plugin">The plugin that called the feedback</param>
        public void Feedback(string Feedback, IPlugin Plugin)
        {
            Options.WriteComment(Feedback);
        }

        public void Act(string action, string[] actionParams)
        {
            string actionInfo = action + "(";
            if (actionParams != null)
            {
                actionInfo += String.Join(",", actionParams);
            }
            Options.WriteComment("(A) " + actionInfo);

            // Look in local actions
            int index = actionNames.IndexOf(action);
            
            // Look in plugin's actions
            try
            {
                this.currentMenuActions[action].Execute(actionParams);
            }
            catch (Exception)
            {
                if (index == -1)
                {
                    Options.WriteComment("(!) Action not found!");
                }
            }

            // Look in local actions
            while (index != -1)
            {
                try
                {
                    actionFunctions[index].Execute(actionParams);
                }
                catch (ArgumentException)
                {
                    Options.WriteComment("(x) Error with arguments");
                }
                catch (InvalidOperationException)
                {
                    Options.WriteComment("(x) Wrong thread!");
                }
                catch
                {
                    Options.WriteComment("(x) Unable to execute this function!");
                }
                index = actionNames.IndexOf(action, index + 1);
            }
        }

        public void Help(string action)
        {
            // Look in local actions
            int index = actionNames.IndexOf(action);

            // Look in plugin's actions
            try
            {
                this.currentMenuActions[action].Help();
            }
            catch (Exception)
            {
                if (index == -1)
                {
                    Options.WriteComment("(!) Action not found!");
                }
            }

            // Look in local actions
            while (index != -1)
            {
                actionFunctions[index].Help();
                index = actionNames.IndexOf(action, index + 1);
            }
        }

        public void RegisterAction(string action, PluginAction actionFunc)
        {
            Options.WriteComment("(!) registered this action in a wrong way: " + action);
            actionNames.Add(action);
            actionFunctions.Add(actionFunc);
        }

        public void RegisterAction(PluginAction actionFunc)
        {
            actionNames.Add(actionFunc.name);
            actionFunctions.Add(actionFunc);
        }

        public void RegisterPluginsActions(Dictionary<string, PluginAction> pf)
        {
            this.currentMenuActions = pf;
        }

        public void SwitchToPlugin(PluginCouple ap)
        {
            ap.Parent.Instance.Activate();
            this.RegisterPluginsActions(ap.Parent.Instance.MenuActions);
        }

        public void GetActionHelp()
        {
            Options.WriteComment("(i) Following plugins are available: ");
            foreach (string s in actionNames)
            {
                Options.WriteComment(" -  " + s);
            }
        }

        public void SwitchToPlugin(string name)
        {
            if (!this.availablePlugins.ContainsKey(name)) return;
            this.availablePlugins[name].Instance.Activate();
            if (this.availablePlugins[name].Instance.MenuActions != null)
                this.RegisterPluginsActions(this.availablePlugins[name].Instance.MenuActions);
        }
    }

    namespace Types
    {
        /// <summary>
        /// Collection for AvailablePlugin Type
        /// </summary>
        public class AvailablePlugins : CollectionBase
        {
            //A Simple Home-brew class to hold some info about our Available Plugins

            /// <summary>
            /// Add a Plugin to the collection of Available plugins
            /// </summary>
            /// <param name="pluginToAdd">The Plugin to Add</param>
            public void Add(Types.AvailablePlugin pluginToAdd)
            {
                this.List.Add(pluginToAdd);
            }

            /// <summary>
            /// Remove a Plugin to the collection of Available plugins
            /// </summary>
            /// <param name="pluginToRemove">The Plugin to Remove</param>
            public void Remove(Types.AvailablePlugin pluginToRemove)
            {
                this.List.Remove(pluginToRemove);
            }

            /// <summary>
            /// Finds a plugin in the available Plugins
            /// </summary>
            /// <param name="pluginNameOrPath">The name or File path of the plugin to find</param>
            /// <returns>Available Plugin, or null if the plugin is not found</returns>
            public AvailablePlugin Find(string pluginNameOrPath)
            {
                AvailablePlugin toReturn = null;

                //Loop through all the plugins
                foreach (Types.AvailablePlugin pluginOn in this.List)
                {
                    //Find the one with the matching name or filename
                    if ((pluginOn.Instance.Name.Equals(pluginNameOrPath)) || pluginOn.AssemblyPath.Equals(pluginNameOrPath))
                    {
                        toReturn = pluginOn;
                        
                        break;
                    }
                }
                return toReturn;
            }
        }

        /// <summary>
        /// Data Class for Available Plugin.  Holds and instance of the loaded Plugin, as well as the Plugin's Assembly Path
        /// </summary>
        public class AvailablePlugin
        {
            //This is the actual AvailablePlugin object.. 
            //Holds an instance of the plugin to access
            //ALso holds assembly path... not really necessary
            private IPlugin myInstance = null;
            private string myAssemblyPath = "";
            private string myName = "";

            public IPlugin Instance
            {
                get { return myInstance; }
                set { myInstance = value; }
            }
            public string AssemblyPath
            {
                get { return myAssemblyPath; }
                set { myAssemblyPath = value; }
            }
            public string Name
            {
                get { return myName; }
                set { myName = value; }
            }
        }
    }
}